package jmpo.cls;

import java.util.Arrays;

/**
 * Мы можем создавать классы, которые содержат методы без реализации.
 * Такие методы мы отмечаем ключевым словом abstract.
 * Причём отмечаем мы не только конкретный метод, но и класс вцелом.
 * Компилятор понимает, что объекты отмеченного класса создать невозможно,
 * так как для отдельных методов нет реализации.
 * Очевидно, что метод не может быть одновременно abstract и final.
 * Класс также не может быть одновременно abstract и final.
 * Но класс может быть sealed и abstract.
 */
abstract class CheckedIntIterator {
    abstract int next();

    final boolean isGood() {
        return status;
    }

    CheckedIntIterator(boolean initialStatus) {
        this.status = initialStatus;
    }

    protected void falseStatus() {
        status = false;
    }

    private boolean status;
}

final class SizedIntIterator extends CheckedIntIterator {
    @Override
    final int next() {
        var res = data[idx++];
        if (idx == data.length) this.falseStatus();
        return res;
    } 

    SizedIntIterator(int[] data) {
        super(data.length > 0);
        this.data = Arrays.copyOf(data, data.length);
        this.idx = 0;
    }

    private int[] data;
    private int idx;
}

final class RingIntIterator extends CheckedIntIterator {
    @Override
    final int next() {
        var res = current;
        current = (current + 1) % mod;
        return res;
    } 

    RingIntIterator(int mod) {
        super(true);
        this.mod = mod;
        this.current = 0;
    }

    private int mod, current;
}

/**
 * sealed abstract example
 * Обратим внимание, что абстрактные методы могут быть protected.
 * Но они не могут быть private, так как о закрытых компонентах 
 * класса другие классы не должны даже знать.
 */
sealed abstract class AbstractVisitor
    permits VisitorA, VisitorB {
    
    abstract String visit(AbstractVisitor shape);

    protected abstract String visitA(VisitorA actualVisitor);
    protected abstract String visitB(VisitorB actualVisitor);
}

final class VisitorA extends AbstractVisitor {

    @Override
    final String visit(AbstractVisitor visitor) {
        return visitor.visitA(this);
    }

    @Override
    final protected String visitA(VisitorA actualVisitor) {
        return "VisitorA visited by VisitorA";
    }

    @Override
    final protected String visitB(VisitorB actualVisitor) {
        return "VisitorB visited by VisitorA";
    }
}
final class VisitorB extends AbstractVisitor {

    @Override
    final String visit(AbstractVisitor visitor) {
        return visitor.visitB(this);
    }

    @Override
    final protected String visitA(VisitorA actualVisitor) {
        return "VisitorA visited by VisitorB";
    }

    @Override
    final protected String visitB(VisitorB actualVisitor) {
        return "VisitorB visited by VisitorB";
    }
}

public class AbstractClass {
    private static boolean sizedIteratorTestA() {
        CheckedIntIterator chit = new SizedIntIterator(new int[] {1, 2, 3});
        return chit.isGood() == true;
    }

    private static boolean sizedIteratorTestB() {
        CheckedIntIterator chit = new SizedIntIterator(new int[0]);
        return chit.isGood() == false;
    }

    private static boolean sizedIteratorTestC() {
        CheckedIntIterator chit = new SizedIntIterator(new int[] {1, 2, 3});
        return chit.next() == 1;
    }

    private static boolean sizedIteratorTestD() {
        CheckedIntIterator chit = new SizedIntIterator(new int[] {1, 2, 3});
        chit.next();
        chit.next();
        return chit.isGood() == true;
    }

    private static boolean sizedIteratorTestE() {
        CheckedIntIterator chit = new SizedIntIterator(new int[] {1, 2, 3});
        chit.next();
        chit.next();
        chit.next();
        return chit.isGood() == false;
    }

    private static boolean visitorTest() {
        AbstractVisitor visitorA = new VisitorA();
        AbstractVisitor visitorB = new VisitorB();
        return visitorA.visit(visitorB).equals("VisitorA visited by VisitorB")
        && visitorB.visit(visitorA).equals("VisitorB visited by VisitorA")
        && visitorA.visit(visitorA).equals("VisitorA visited by VisitorA")
        && visitorB.visit(visitorB).equals("VisitorB visited by VisitorB");

    }

    private static void test(boolean expectTrue) {
        if(expectTrue) {
            System.out.println("Ok!");
            return;
        }
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        test(sizedIteratorTestA());
        test(sizedIteratorTestB());
        test(sizedIteratorTestC());
        test(sizedIteratorTestD());
        test(sizedIteratorTestE());
        test(visitorTest());
    }
}
